/**
 * Copyright (c) 2008 Andrew Rapp. All rights reserved.
 *  
 * This file is part of XBee-API.
 *  
 * XBee-API is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * XBee-API is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with XBee-API.  If not, see <http://www.gnu.org/licenses/>.
 */
 
import processing.serial.*;
import java.util.concurrent.*;

Node nodes[];    //IMPLEMENT: Possibly read known nodes from a text-file
Message message;

XBee xbee;
Queue<XBeeResponse> queue = new ConcurrentLinkedQueue<XBeeResponse>();
XBeeResponse response;

//String xbeePort = "tty.usbserial-A800cFqt";
String xbeePort;

void setupXBee() 
{
  message =  new Message();  
  xbeePort = XBEE_PORT;
  PropertyConfigurator.configure(dataPath("")+"log4j.properties");
  
  try 
  { 
    xbee = new XBee();
    xbee.open(xbeePort, 115200);
  } 
  catch (Exception e) 
  {
    System.out.println("XBee failed to initialize");
    e.printStackTrace();
    System.exit(1);
  }

  xbee.addPacketListener(new PacketListener() 
  {
      public void processResponse(XBeeResponse response) 
      {
        queue.offer(response);
      }
  }  );
  
}

void checkXBee() 
{
  try 
  { 
    readXBee(); 
  } 
  catch (Exception e)
  {
    e.printStackTrace(); 
  }
}

///////////////////////////////////////////////////////////////////////////////////////

//void sendToXBee(int temp) {
void sendToXBee( Message m ) 
{
  try 
  {
    XBeeAddress64 addr64 = new XBeeAddress64( m.getAddress64() ); 
    XBeeAddress16 addr16 = new XBeeAddress16( get16BitAddress(addr64).getAddress() );

    // Check whether we will send a BROAD or UNI cast to determine the type
    XBeeAddress64 broadcastAddress = new XBeeAddress64("00 00 00 00 00 00 FF FF");
    XBeeAddress16 unknown16B = new XBeeAddress16(0xff,0xfe);
    
    ZNetTxRequest.Option cast = ZNetTxRequest.Option.UNICAST;
    
    if ( addr64.equals(broadcastAddress) )
    { 
       cast = ZNetTxRequest.Option.BROADCAST; 
       print(hour() + ":" + minute() + ":" + second() + " \t BROADCASTING");
    }
    else {
      print(hour() + ":" + minute() + ":" + second() +" \t UNICASTING to " +  getNodeName(addr64) + " \t " + ByteUtils.toBase16(addr16.getAddress()) + " / " + ByteUtils.toBase16(addr64.getAddress()));
    }
    
    ZNetTxRequest request;
    
    if (addr16.equals( unknown16B ) ) {
      request = new ZNetTxRequest(addr64, m.getPayload()); 
      println(" \t  >> using 64 bit address ");
    }
    else {
      println(" \t >>  using 16 bit address ");
      request = new ZNetTxRequest(    
                       0x01,    // frameID
                       addr64,  // 64b Address
                       addr16,  // 16b Address
                       0 ,      // broadcastRadius
                       cast ,   // ZNetTxRequest.Option (UNICAST or BROADCAST
                       m.getPayload()); // int[] payload
    }
//    println("zb request is " + request.getXBeePacket().getPacket());
//    println("sending tx " + request);
//    println("request packet bytes (base 16) " + ByteUtils.toBase16(request.getXBeePacket().getPacket()));
    long start = System.currentTimeMillis();
//    println("sending tx packet: " + request.toString());
//    xbee.sendAsynchronous(request);
    try 
    {
      ZNetTxStatusResponse response = (ZNetTxStatusResponse) xbee.sendSynchronous(request, 1500);
      // update frame id for next request // not required since we will be sending new requests 
      // request.setFrameId(xbee.getNextFrameId());
      // println("received response " + response);
      // println("status response bytes:" + ByteUtils.toBase16(response.getPacketBytes()));
      if (response.getDeliveryStatus() == ZNetTxStatusResponse.DeliveryStatus.SUCCESS) 
      {
        // the packet was successfully delivered
         //if (response.getRemoteAddress16().equals(XBeeAddress16.ZNET_BROADCAST)) {
          for (int i = 0; i < nodes.length; i++) { 
            if (addr64.equals(nodes[i].addr64))
            {
            // specify 16-bit address for faster routing?.. really only need to do this when it changes
              XBeeAddress16 remoteAddr16 = response.getRemoteAddress16();
              if (!remoteAddr16.equals(nodes[i].addr16)  && addr64 != broadcastAddress) // only set it when it is not already the same and when it is not a broadcast message
              {
                nodes[i].setAddress16(remoteAddr16.getAddress());
                println("\t  setting new 16 bit address: " + remoteAddr16);
              }
            }
          }
        //}                                                    
      }
      else 
      {
        // packet failed.  log error
        // it's easy to create this error by unplugging/powering off your remote xbee.  when doing so I get: packet failed due to error: ADDRESS_NOT_FOUND  
        println("\t  packet failed due to error: " + response.getDeliveryStatus());
      }
      // I get the following message: Response in 75, Delivery status is SUCCESS, 16-bit address is 0x08 0xe5, retry count is 0, discovery status is SUCCESS 
      println("\t  Response in " + (System.currentTimeMillis() - start) + ", Delivery status is " + response.getDeliveryStatus() + ", 16-bit address is " + ByteUtils.toBase16(response.getRemoteAddress16().getAddress()) + ", retry count is " +  response.getRetryCount() + ", discovery status is " + response.getDeliveryStatus());
      
    }
    catch (XBeeTimeoutException e) {
      println("\t  Request timed out for " + getNodeName(addr64) + " \t >> " + addr64 + " / " +  addr16 );
      if ( !addr16.equals(unknown16B) ) {
       for (int i = 0; i < nodes.length; i++) { 
          if (addr64.equals(nodes[i].addr64))
          {
            int[] resetAdd = {0xff,0xfe};
            nodes[i].setAddress16( resetAdd );
            println("\t  >> resetting 16 bit address");
          }
       }
      }
    }
  }
  catch (Exception e) 
  {
    println("Unexpected TX Error in main sender try");
  }
}

void readXBee() 
{
  while ((response = queue.poll()) != null) 
  {
    // we got something!
    try 
    {
      if (response.getApiId() == ApiId.ZNET_RX_RESPONSE) 
      {
        // we received a packet from ZNetSenderTest.java
        ZNetRxResponse rx = (ZNetRxResponse) response;
        
        //println("Received RX packet, option is " + rx.getOption() + ", sender 64 address is " + ByteUtils.toBase16(rx.getRemoteAddress64().getAddress()) + ", remote 16-bit address is " + ByteUtils.toBase16(rx.getRemoteAddress16().getAddress()) + ", data is " + ByteUtils.toBase16(rx.getData()));
        int[] data = rx.getData();
        XBeeAddress64 sender = rx.getRemoteAddress64();
        try 
        { 
          
          int scope = (data[0] << 8) + data[1];
          int funcID = data[2];
          
          int arguments[] = new int[(data.length-3)/2];

          for (int i = 0; i < arguments.length; i++) 
          {
            int pos      =  (i*2) + 3;
            arguments[i] =  (data[pos] << 8) + data[pos+1];
          }
          processData(funcID, arguments, scope, sender);
        }
        
        catch(Exception e) { }
      }

      else if (response.getApiId() == ApiId.ZNET_TX_STATUS_RESPONSE) {
        //println("received status repsonse IN RECEIVE");
      }
      else if (response.getApiId() == ApiId.ZNET_IO_NODE_IDENTIFIER_RESPONSE) {
        ZNetNodeIdentificationResponse rx = (ZNetNodeIdentificationResponse) response;
        String m = "Node reporting in " + getNodeName(rx.getRemoteAddress64()) + " > " + rx.getRemoteAddress16() + " / " + rx.getRemoteAddress64();
        println(m);
        for (int i = 0; i < nodes.length; i++) { 
          
          if (rx.getRemoteAddress64().equals(nodes[i].addr64))
          {
            nodes[i].setAddress16( rx.getRemoteAddress16().getAddress() );
            println("\t  >> setting 16 bit address");
          }
        }
      }      
      else {
        println("received unexpected packet " + response.toString());
      }
    }
    catch (Exception e) 
    {
      println("error:" + e);
    }
  }
}



